#include "q_shared.h"
#include "bg_global.h"
#include "bg_gamex86funcs.h"
#include "bg_hooks.h"
#include <stdlib.h>
#include "g_local.h"

int (__cdecl *syscall)( int arg, ... ) = (int (__cdecl *)( int, ...))-1;
static vmMainCGAME vmGameX;
static dllEntryCGAME dllEntryX;
static GetGameAPIGAME GetGameAPIX;

DWORD vmMainAddy;
DWORD dllEntryAddy;
DWORD GetGameAPIAddy;
DWORD jagamex86BaseAddy;

static qboolean loadedFromBase = qtrue;

static void __stdcall Attach(qboolean useGI);

void SetupProxyHooks( void )
{
	if(loadedFromBase || !proxyDLL)
	{
		return;
	}
	onCGInit = (onCGInitX)GetProcAddress(proxyDLL, "onCGInit");
	onCGShutdown = (onCGShutdownX)GetProcAddress(proxyDLL, "onCGShutdown");
	onCGDrawActiveFrame = (onCGDrawActiveFrameX)GetProcAddress(proxyDLL, "onCGDrawActiveFrame");
	onCGConsoleCommand = (onCGConsoleCommandX)GetProcAddress(proxyDLL, "onCGConsoleCommand");
	OnGamesideInit = (OnGamesideInitX)GetProcAddress(proxyDLL, "OnGamesideInit");
	OnGamesideInit();
}


//CGAME
__declspec(dllexport) void dllEntry( int (__cdecl  *syscallPtr)( int arg,... ) ) {
	syscall = syscallPtr;
	//Unknown code here..just going to call SP's dllEntry for now since unknown
	dllEntryX(syscallPtr);
	gi->Printf("^2ProxyDLL: dllEntry OK\n");
	Attach(qtrue);
}

__declspec(dllexport) int vmMain(int command, int arg0, int arg1, int arg2, int arg3, int arg4, int arg5, int arg6, int arg7)
{
	if(!loadedFromBase && proxyDLL)
	{
		switch(command)
		{
			case CG_INIT:
				if(onCGInit)
				{
					onCGInit();
				}
				break;
			case CG_SHUTDOWN:
				if(onCGShutdown)
				{
					onCGShutdown();
				}
				break;
			case CG_CONSOLE_COMMAND:
				//OK, this gets handled a little differently..vmMain returns qfalse if no cmd. First off, we need to check if the func exists..tricky.
				if(GetProcAddress(proxyDLL, "onCGConsoleCommand"))
				{
					int vmMainRet = vmGameX(command, arg0, arg1, arg2, arg3, arg4, arg5, arg6, arg7); //I'm also lazy and don't have time to do this atm
					if(!vmMainRet)
					{
						return onCGConsoleCommand();
					}
				}
				break;
			case CG_DRAW_ACTIVE_FRAME:
				if(onCGDrawActiveFrame)
				{
					onCGDrawActiveFrame(arg1, (DWORD *)arg2);
				}
				break;
		}
	}
	return vmGameX(command, arg0, arg1, arg2, arg3, arg4, arg5, arg6, arg7);
}

//GAME
__declspec(dllexport) game_export_t *GetGameAPI(game_import_t *import)
{
	gi = import;
	globals = GetGameAPIX(import);
	{
		int globalssize = globals->gentitySize;
		int ourSize = sizeof(gentity_t);
		gi->Printf("^2ProxyDLL: GetGameAPI OK\n");
		gi->Printf("ProxyDLL: sizeof(gentity_t): 0x%X\n", globalssize);
		gi->Printf("ProxyDLL: sizeof(your gentity_t): 0x%X\n", ourSize);
	}
	HookGIFuncs( );
	return globals;
}

static void __stdcall Attach(qboolean useGI)
{
	if(!useGI)
	{
		jagamex86 = LoadLibrary(TEXT("ProxyDLL.dll"));
		jagamex86BaseAddy = (DWORD)GetModuleHandle("ProxyDLL.dll");
	}
	else
	{
		char buffer[MAX_QPATH];
		char buf2[MAX_QPATH];
		
		//Print out our addresses
		gi->Printf("Fake DLL Base Address: 0x%x\n", jagamex86BaseAddy);
		gi->Printf("vmMain: 0x%x\n", vmMainAddy);
		gi->Printf("GetGameAPI: 0x%x\n", GetGameAPIAddy);
		gi->Printf("dllEntry: 0x%x\n", dllEntryAddy);

		gi->Cvar_VariableStringBuffer("fs_game", buffer, MAX_QPATH);
		sprintf(buf2, "%s/jagamex86.dll", buffer);
		proxyDLL = LoadLibrary(TEXT(buf2));
		if(!proxyDLL)
		{
			gi->Printf("ProxyDLL: No VM found in mod folder, using default...\n");
			proxyDLL = LoadLibrary(TEXT("base/jagamex86.dll"));
			loadedFromBase = qtrue;
			gi->Printf("Base DLL base address: 0x%x\n", (DWORD)GetModuleHandle("base/jagamex86.dll"));
		}
		else
		{
			gi->Printf("Mod DLL base address: 0x%x\n", (DWORD)GetModuleHandle(buf2));
			loadedFromBase = qfalse;
			SetupProxyHooks();
		}
		gi->Printf("^2Proxy DLL loaded successfully\n");

		//Load stuff from the serverside
		G_SpawnItem = (G_SpawnItemX)DLLA(0x2006F290, jagamex86);
		G_CheckInSolid = (G_CheckInSolidX)DLLA(0x200A0320, jagamex86);
		vtos = (vtosX)DLLA(0x2009EAD0, jagamex86);
		TAG_Add = (TAG_AddX)DLLA(0x2008F470, jagamex86);
		NAV__SpawnedPoint = (NAV__SpawnedPointX)DLLA(0x200867D0, jagamex86);
		G_FreeEntity = (G_FreeEntityX)DLLA(0x2009EC60, jagamex86);
		BG_FindItemForWeapon = (BG_FindItemForWeaponX)DLLA(0x200F55D0, jagamex86);
		G_Unknown1 = (G_Unknown1X)DLLA(0x200783A0, jagamex86);
		G_SetOrigin = (G_SetOriginX)DLLA(0x2009F3B0, jagamex86);
		G_SetAngles = (G_SetAnglesX)DLLA(0x2009F8B0, jagamex86);
		G_ModelIndex = (G_ModelIndexX)DLLA(0x2009E7B0, jagamex86);

		waypoint_testDirection = (waypoint_testDirectionX)DLLA(0x2007F3E0, jagamex86);
//		MoveTrace = (MoveTraceX)0x2007FC00;
	}
	if(!jagamex86)
	{
		//todo: Call Com_Error direct from JASP.exe
		trap_Error("PROXY: Could not load module\n");
		return;
	}
	if(!useGI)
	{
		//Basic/absolutely essential funcs
		
		vmGameX = (vmMainCGAME)(GetProcAddress(jagamex86, "vmMain"));
		vmMainAddy = (DWORD)(GetProcAddress(jagamex86, "vmMain"));
		dllEntryX = (dllEntryCGAME)(GetProcAddress(jagamex86, "dllEntry"));
		dllEntryAddy = (DWORD)(GetProcAddress(jagamex86, "dllEntry"));
		GetGameAPIX = (GetGameAPIGAME)(GetProcAddress(jagamex86, "GetGameAPI"));
		GetGameAPIAddy = (DWORD)(GetProcAddress(jagamex86, "GetGameAPI"));
		CG_Argv = (CG_ArgvX)DLLA(0x2012B460, jagamex86);
		//Extra/fun funcs
	}
}

static void __stdcall Release(void)
{
	FreeLibrary(jagamex86);		//maybe not necessary
	FreeLibrary(proxyDLL);
}

int __stdcall DllMain(HANDLE hModule, DWORD dwReason, LPVOID lpReserved)
{
	switch(dwReason)
	{
		case DLL_PROCESS_ATTACH: 
		//TODO: Logging
		Attach(qfalse);
		break;

		case DLL_PROCESS_DETACH:
		Release();
		break;
	}
	return 1;
}

__declspec (dllexport) int __stdcall DllEntryPoint(HANDLE hModule, DWORD dwReason, LPVOID lpReserved)
{
	switch(dwReason)
	{
		case DLL_PROCESS_ATTACH: 
		//TODO: Logging
		Attach(qfalse);
		break;

		case DLL_PROCESS_DETACH:
		Release();
		break;
	}
	return 1;
}

void UnhookAll( void )
{
	int i;
	for(i = 0; i < HOOK_MAX; i++)
	{
		BG_RemovePatch(&hooks[i]);
	}
}

void ClientCommand( int clientNum )
{
	gi = (game_import_t *)DLLA(0x201F4C48, jagamex86);
	if(!Q_stricmp(gi->argv(0), "testsizes"))
	{
		gi->Printf("ent->health = %i\n", globals->gentities->health);
		gi->Printf("ent->max_health = %i\n", globals->gentities->max_health);
		gi->Printf("ent->client = %X\n", globals->gentities->client);
		gi->Printf("ent->classname = %s\n", globals->gentities->classname);
		gi->Printf("ent->absmin = { %f, %f, %f }\n", globals->gentities->absmin[0], globals->gentities->absmin[1], globals->gentities->absmin[2]);
		return;
	}
	else if(!Q_stricmp(gi->argv(0), "itemcheck"))
	{
		if(gi->argc() < 2)
		{
			gi->Printf("Need more args.\n");
			return;
		}
		{
			int arg2 = atoi(gi->argv(1));
			if(arg2 >= 0 && arg2 <= 4)
			{
				if((*((DWORD *)globals->gentities->client + (428*arg2)) >= 5))
				{
					gi->Printf("Nope.\n");
					return;
				}
			}
			gi->Printf("Item can be grabbed!");
			return;
		}
	}
	ClientCommandX( clientNum );
}

void ShutdownGame( void )
{
	UnhookAll();
	ShutdownGameX();
}